/**
 * $Id: PersonnelDao.java 9 2011-10-05 14:12:06Z adriftrock@gmail.com $
 */

package com.swehr.server.daos.impl;

import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Resource;
import com.swehr.server.daos.IPersonnelDao;
import com.swehr.server.models.Personnel;
import com.swehr.utils.DateUtil;
import com.swehr.utils.StringUtil;
import org.apache.log4j.Logger;

import javax.inject.Inject;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Rock Huang
 * @version 0.1
 */
public class PersonnelDao implements IPersonnelDao {
    private static Logger logger = Logger.getLogger(PersonnelDao.class);
    private InferenceDao inf;

    public PersonnelDao() {
    }

    @Inject
    public void setInf(InferenceDao inf) {
        this.inf = inf;
    }

    public List<Personnel> findByOrg(String oid) {
        StringBuffer s = new StringBuffer(128);
        s.append(Const.URI_ORG + " ehr:hasPerson ?psn . \n");
        SparqlBuilder.replace(s, "@id", oid);
        return findByCond(s);
    }

    public List<Personnel> findByPos(String pid) {
        StringBuffer s = new StringBuffer(128);
        s.append(Const.URI_POS + " ehr:isAppointedBy ?psn . \n");
        SparqlBuilder.replace(s, "@id", pid);
        return findByCond(s);
    }

    public List<Personnel> findByCond(StringBuffer cond) {
        StringBuffer cmd = new StringBuffer(1024);
        cmd.append("SELECT distinct * WHERE{ \n");
        cmd.append("  ").append(cond);
        cmd.append("?t rdfs:subClassOf ehr:Person . ?psn a ?t . \n");
        cmd.append("  ?psn ehr:hasId ?id . \n");
        cmd.append("  ?psn ehr:hasName ?nm . \n");
        cmd.append("  ?psn ehr:hasGender ?gd . \n");
        cmd.append("  ?psn ehr:hasBirthday ?bd . \n");
        cmd.append("  OPTIONAL { ?psn ehr:isAppointedTo ?pos } . \n");
        cmd.append("  OPTIONAL { ?psn ehr:hasEmail ?em } . \n");
        cmd.append("  OPTIONAL { ?psn ehr:hasPhoneNumber ?pn } . \n");
        cmd.append("   }");

        List<Personnel> list = new ArrayList<Personnel>();

        QueryExecution qe = null;
        try {
            qe = inf.create(cmd.toString());
            ResultSet rs = qe.execSelect();
            while (rs.hasNext()) {
                list.add(one(rs.nextSolution()));
            }
        } catch (Exception e) {
            logger.error(e);
            e.printStackTrace();
        } finally {
            if (qe != null) {
                qe.close();
            }
        }

        return list;
    }

    public List<Personnel> find() {
        StringBuffer cmd = new StringBuffer(1024);
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        cmd.append("SELECT * FROM ").append(Const.GRAPH_PSN + "\n");
        cmd.append("WHERE{ \n");
        cmd.append("   GRAPH ").append(Const.GRAPH_PSN).append(" {\n");
        cmd.append("    ?psn ehr:hasId ?id . \n");
        cmd.append("    ?psn a ?t .\n");
        cmd.append("    ?psn ehr:hasName ?nm . \n");
        cmd.append("    ?psn ehr:hasGender ?gd . \n");
        cmd.append("    ?psn ehr:hasBirthday ?bd . \n");
        cmd.append("    OPTIONAL { ?psn ehr:isAppointedTo ?pos } . \n");
        cmd.append("    OPTIONAL { ?psn ehr:hasEmail ?em } . \n");
        cmd.append("    OPTIONAL { ?psn ehr:hasPhoneNumber ?pn } . \n");
        cmd.append("   }\n}");
        List<Personnel> list = new ArrayList<Personnel>();
        try {
            ResultSet rs = conn.executeQuery(cmd.toString());
            while (rs.hasNext()) {
                list.add(one(rs.nextSolution()));
            }
        } catch (Exception e) {
            logger.error("Failed to find Personnel list.", e);
            e.printStackTrace();
        } finally {
            conn.close();
        }

        return list;
    }

    private Personnel one(QuerySolution qs) throws ParseException {
        Personnel psn = new Personnel();
        psn.setId(qs.getLiteral("?id").getInt());
        psn.setType(qs.getResource("?t").getLocalName());
        psn.setName(qs.getLiteral("?nm").getString());
        psn.setGender(qs.getLiteral("?gd").getString());
        psn.setBirthday(DateUtil.parse(qs.getLiteral("?bd").getString()));
        Resource r = qs.getResource("?pos");
        psn.setPosUri(r != null ? r.getURI() : null);
        Literal em = qs.getLiteral("?em");
        psn.setEmail(em != null ? em.getString() : "");
        Literal pn = qs.getLiteral("?pn");
        psn.setPhoneNumber(pn != null ? pn.getString() : "");
        logger.debug("Get one: " + psn.toString());
        return psn;
    }

    public Personnel findById(int psnId) {
        StringBuffer cmd = new StringBuffer(1024);
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        cmd.append("SELECT * ");
        cmd.append("WHERE{ \n");
        cmd.append("   GRAPH ").append(Const.GRAPH_PSN).append(" {\n");
        cmd.append("    ?psn ehr:hasId ?id . \n");
        cmd.append("    ?psn a ?t .\n");
        cmd.append("    ?psn ehr:hasName ?nm . \n");
        cmd.append("    ?psn ehr:hasGender ?gd . \n");
        cmd.append("    ?psn ehr:hasBirthday ?bd . \n");
        cmd.append("    OPTIONAL { ?psn ehr:isAppointedTo ?pos } . \n");
        cmd.append("    OPTIONAL { ?psn ehr:hasEmail ?em } . \n");
        cmd.append("    OPTIONAL { ?psn ehr:hasPhoneNumber ?pn } . \n");
        cmd.append("    ?psn ehr:hasId ").append(psnId).append("\n");
        cmd.append("   }\n}");
        Personnel psn = null;
        try {
            ResultSet rs = conn.executeQuery(cmd.toString());
            psn = one(rs.nextSolution());
        } catch (Exception e) {
            logger.error("findById(), failed Operation: " + e);
        } finally {
            conn.close();
        }
        return psn;
    }

    /**
     * Delete triples in graph Person according to given ID.
     *
     * @param psnId A identifier of Person.
     * @return boolean To indicate the operation is successful or not.
     */
    public boolean delete(int psnId) {
        StringBuffer cmd = new StringBuffer(512);
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        cmd.append("WITH").append(Const.GRAPH_PSN + "\n");
        cmd.append("DELETE { ?o ?p ?v }\n");
        cmd.append("WHERE{ \n");
        cmd.append("    ?o ?p ?v ; ehr:hasId ").append(psnId).append("\n}");
        return conn.execUpdate(cmd.toString(), inf);
    }

    public boolean batchSave(List<Personnel> created) throws Exception {
        StringBuffer cmd = new StringBuffer();
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        cmd.append("INSERT DATA {\n");
        cmd.append("  GRAPH ").append(Const.GRAPH_PSN).append(" { \n");

        for (Personnel e : created) {
            if (e.getId() != 0) {
                throw new IllegalArgumentException("Personnel.ID should be 0");
            }
            cmd.append("    " + Const.URI_PSN).append(" rdf:type ehr:Employee ;\n");
            cmd.append("    ehr:hasId @id ; \n");
            cmd.append("    ehr:hasName \"").append(e.getName()).append("\" ; \n");
            cmd.append("    ehr:hasGender \"").append(e.getGender()).append("\" ; \n");
            cmd.append("    ehr:hasBirthday  \"").append(DateUtil.format(e.getBirthday())).append("\"^^xsd:dateTime ; \n");
            cmd.append("    ehr:hasEmail \"").append(e.getEmail()).append("\" ; \n");
            if (e.getPhoneNumber() != null) {
                cmd.append("    ehr:hasPhoneNumber \"").append(e.getPhoneNumber()).append("\" ; \n");
            }
//            if (e.getOrgUri() != null) {
//                cmd.append("    ehr:isEmployeeOf ").append(SparqlBuilder.wrapOrg(e.getOrgUri())).append(" ; \n");
//            }
            if (e.getPosUri() != null) {
                cmd.append("    ehr:isAppointedTo ").append(SparqlBuilder.wrapPos(e.getPosUri())).append(" ; \n");
            }

            int id = IdGenerator.getInstance().getMaxPsnId();
            SparqlBuilder.replace(cmd, "@id", String.valueOf(++id));
            SparqlBuilder.replace(cmd, "@id", String.valueOf(id));
            IdGenerator.getInstance().setMaxPsnId(id);
            SparqlBuilder.replaceLast(cmd, ";", ".");
        }
        cmd.append("  }\n}");

        return conn.execUpdate(cmd.toString(), inf);
    }

    public boolean batchUpdate(List<Personnel> updated) {
        boolean change = false;
        StringBuffer cmd = new StringBuffer(1024);
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);

        for (Personnel e : updated) {
            // if fields are empty, the sparql will be still generated.
            // This segment will avoid the case.
            if (StringUtil.isEmptyOrNull(e.getName(), e.getEmail(),
                    e.getGender(), e.getPhoneNumber(), e.getPosUri())) {
                continue;
            }

            Personnel old = findById(e.getId());
            cmd.append("WITH ").append(Const.GRAPH_PSN + "\n");
            cmd.append("DELETE { @pos@t@nm@gd@em@pn@bd } \n");
            cmd.append("INSERT { @pos@t@nm@gd@em@pn@bd } \n");
            cmd.append("WHERE {\n");
            cmd.append("  ?o a ehr:").append(old.getType()).append(" .\n");
            cmd.append("  ?o ehr:hasId @id");
            cmd.append("\n};\n");

            prepareUpdateSparql(old, e, cmd);
            change = true;
        }

        return !change || conn.execUpdate(cmd.toString(), inf);
    }

    private void prepareUpdateSparql(Personnel old, Personnel e, StringBuffer cmd) {
        // check if the given Personnel object has no id value then return.
        if (e.getId() <= 0) {
            throw new NullPointerException("Cannot found ID for Personnel update");
        } else {
            int p = cmd.indexOf("@id");
            cmd.replace(p, p + 3, String.valueOf(e.getId()));
        }

        // compare with old one and prepare partial SPARQL
        boolean s = false; // if needs separator

        // todo: need to consider the case of 'isEmployerOf'
        s = SparqlBuilder.replace(cmd, "@pos", "isAppointedTo",
                SparqlBuilder.wrapPos(old.getPosUri()),
                SparqlBuilder.wrapPos(e.getPosUri()), s);
        s = SparqlBuilder.replace(cmd, "@t", "rdf:type", old.getType(), e.getType(), s);
        s = SparqlBuilder.replace(cmd, "@nm", "hasName", old.getName(), e.getName(), s);
        s = SparqlBuilder.replace(cmd, "@gd", "hasGender", old.getGender(), e.getGender(), s);
        s = SparqlBuilder.replace(cmd, "@em", "hasEmail", old.getEmail(), e.getEmail(), s);
        s = SparqlBuilder.replace(cmd, "@pn", "hasPhoneNumber", old.getPhoneNumber(), e.getPhoneNumber(), s);

        String ob = DateUtil.format(old.getBirthday()); // old birthday
        String nb = e.getBirthday() == null ? ob : DateUtil.format(e.getBirthday()); // new birthday
        boolean change = SparqlBuilder.isModified(nb, ob);
        SparqlBuilder.replace(cmd, "@bd", "hasBirthday@datetime", ob, nb, s, change);
    }

    public boolean batchRemove(String[] removed) {
        StringBuffer cmd = new StringBuffer();
        ConnectDao conn = new ConnectDao();
        conn.setDefaultNS(cmd);
        for (String id : removed) {
            int i = Integer.parseInt(id);
            if (i <= 0) {
                throw new NullPointerException("Cannot found Person ID for RM");
            }
            cmd.append("WITH ").append(Const.GRAPH_PSN + "\n");
            cmd.append("DELETE { ?o ?p ?v }\n");
            cmd.append("WHERE { \n");
            cmd.append("    ?o ?p ?v ; ehr:hasId ").append(i);
            cmd.append("\n};\n");
        }
        return conn.execUpdate(cmd.toString(), inf);
    }

}